<template>
  <Card class="full-height full-width topology-card">
    <template #title>
      业务请求调用链路：
      <Select
        v-model:value="selectedRequsetValue"
        style="width: 200px"
        :options="selectOptions"
        @change="changeRequest"
      ></Select>
      <Button
        type="primary"
        size="small"
        @click="onClickButton"
        style="margin-left: 10px"
        target="_blank"
        >业务入口</Button
      >
      <SettingTwoTone
        @click="openRequestSettingModal"
        style="margin-left: 10px"
      />
    </template>

    <div
      id="d3-container"
      class="full-height full-width"
      style="flex-grow: 1; overflow: hidden"
    ></div>
    <RequestModal ref="requestModalRef" />
  </Card>
</template>

<script lang="ts">
import * as d3 from "d3";
import * as dagreD3 from "dagre-d3";
import injectedNodeSvg from "../../../assets/topology-node/injectedNode.svg";
import injectingNodeSvg from "../../../assets/topology-node/injectingNode.svg";
import runningNodeSvg from "../../../assets/topology-node/runningNode.svg";
import { selectOptions, getData, remainingData } from "./dataJson";
import { Card, Select, Button } from "ant-design-vue";
import { ref } from "vue";
import _ from "lodash";
import { InstanceItem } from "@/type/instances";
import eventBus from "../../../utils/eventBus";
import { getChaosStatusApi } from "@/api/chaos";
import { useInstanceModuleStore } from "@/store/instance";
import { useBusinessRequestModuleStore } from "@/store/businessRequests";
import RequestModal from "./RequsetModal.vue";
import { SettingTwoTone } from "@ant-design/icons-vue";
export default {
  name: "DagreDirectedGraph",
  components: {
    Select,
    Card,
    Button,
    RequestModal,
    SettingTwoTone,
  },
  data() {
    const selectedRequsetValue = ref(selectOptions[0].value);
    const requestData = ref<InstanceItem[]>([]);
    const nodeWidth = 45;
    const nodeHeight = 45;
    const injectedChaos = ref<any>(null);
    const selectedRequset = ref<any>(selectOptions[0]);
    const pollingInterval = ref<any>(null);
    requestData.value = getData(selectedRequsetValue.value);
    useBusinessRequestModuleStore().setBusinessRequest(selectedRequset.value);
    return {
      selectedRequsetValue,
      nodeWidth,
      nodeHeight,
      requestData,
      remainingData,
      getData,
      selectOptions,
      pollingInterval,
      injectedChaos,
      selectedRequset,
    };
  },
  created() {
    this.startPolling();
  },
  mounted() {
    this.createForceDirectedGraph();

    eventBus.on("after-inject", (data) => {
      this.injectedChaos = data;
      this.updateNodeAndLinks(data.params[0].value, data.status);
    });
    eventBus.emit("change-request", this.selectOptions[0]);
    // eventBus.on("restart", (data) => {
    //   d3.select("#d3-container").select("svg").remove();
    //   this.createForceDirectedGraph();
    // });
  },
  methods: {
    updateNodeAndLinks(nodeName: string, status: string) {
      const imgElement = document.querySelector(
        "g#node-" + nodeName + ".node image"
      );
      if (imgElement) {
        imgElement.setAttribute("href", this.getNodeBystatus(status));
        imgElement.setAttribute("xlink:href", this.getNodeBystatus(status));
      }
      d3.selectAll(`g[id^='edge-${nodeName}']`)
        .select("path")
        .transition()
        .duration(300)
        .style("stroke", this.getColorBystatus(status));
      d3.selectAll(`g[id^='edge-${nodeName}']`)
        .select("defs")
        .select("path")
        .transition()
        .duration(300)
        .style("stroke", this.getColorBystatus(status))
        .style("fill", this.getColorBystatus(status));
    },
    getNodeBystatus(status: string) {
      switch (status) {
        case "injectFailed":
          return runningNodeSvg;
          break;
        case "running":
          return runningNodeSvg;
          break;
        case "injecting":
          return injectingNodeSvg;
          break;
        case "injected":
          return injectedNodeSvg;
          break;
      }
    },
    getColorBystatus(status: string) {
      switch (status) {
        case "running":
          return "#8bd496";
          break;
        case "injectFailed":
          return "#8bd496";
          break;
        case "injecting":
          return "#f7c01a";
          break;
        case "injected":
          return "#EF233C";
          break;
      }
    },
    createForceDirectedGraph() {
      const vm = this;
      const container = d3.select("#d3-container");
      const svg = container
        .append("svg")
        .attr("width", "100%")
        .attr("height", "100%");
      const inner = svg.append("g");
      const defs = svg.append("svg:defs");

      const g = new dagreD3.graphlib.Graph()
        .setGraph({})
        .setDefaultEdgeLabel(function () {
          return {};
        });
      this.requestData.forEach((data) => {
        g.setNode(data.name, {
          label: data.name,
          width: this.nodeWidth,
          height: this.nodeHeight,
          status: data.status,
          id: "node-" + data.name,
        });
        data.next.forEach((next) => {
          if (!g.hasNode(next)) {
            g.setNode(next, {
              label: next,
            });
          }
          g.setEdge(data.name, next, {
            id: "edge-" + data.name + "-" + next,
            arrowhead: "vee",
            arrowheadStyle: () => {
              const injectedChaos = useInstanceModuleStore().getInstance;
              let color = "#8bd496";
              if (injectedChaos?.params[0]?.value === data.name) {
                color = this.getColorBystatus(injectedChaos.status);
              }
              return `fill:${color};stroke: none;`;
            },
            curve: d3.curveBasis,
            arrowheadId: "arrow-" + data.name + "-" + next,
          });
        });
      });

      // 创建渲染器并准备 SVG 容器
      var render = new dagreD3.render();
      // 运行渲染器
      render(inner, g);
      // 高亮状态
      const selectedNode = useInstanceModuleStore().getSelectedNode;
      const injectedChaos = useInstanceModuleStore().getInstance;
      if(!selectedNode && injectedChaos){
        useInstanceModuleStore().setSelectedNode(injectedChaos)
      }
      inner
        .selectAll("g.node")
        .append("circle")
        .attr("x", -this.nodeWidth / 2)
        .attr("y", -this.nodeHeight / 2)
        .attr("r", 25)
        .attr("width", this.nodeWidth)
        .attr("height", this.nodeHeight)
        .style("rx", "50%")
        .style("ry", "50%")
        .style("fill", "none")
        .style("stroke", "none")
          .each(function (d: any, i: number) {
          if (d === selectedNode) {
            d3.select(this)
              .transition()
              .ease(d3.easeExpOut) 
              .duration(1000) 
              .style("fill", "rgba(255, 208, 0, 0.3)")
              .style("stroke", "rgba(255, 208, 0, 0.2)")
              .style("stroke-width", "15px");
          }
        });

      // 自定义节点样式为图像
      inner
        .selectAll("g.node")
        .append("image")
        .attr("xlink:href", (d) => {
          if (injectedChaos?.params[0]?.value === d) {
            return this.getNodeBystatus(injectedChaos.status);
          }
          return runningNodeSvg;
        })
        .attr("width", this.nodeWidth)
        .attr("height", this.nodeHeight);

      inner
        .selectAll("g.node")
        .selectAll("g.node rect")
        .style("fill", "none")
        .style("stroke", "none");
      inner.selectAll("g.node tspan").style("fill", "#555555");

      var svgWidth = +svg.node().getBoundingClientRect().width;
      var svgHeight = +svg.node().getBoundingClientRect().height;

      var zoomScale = Math.min(
        svgWidth / g.graph().width,
        svgHeight / g.graph().height
      );

      var xCenterOffset = (svgWidth - g.graph().width * zoomScale) / 2;
      var yCenterOffset = (svgHeight - g.graph().height * zoomScale) / 2;
      inner
        .selectAll("g.node image")
        .attr("x", -this.nodeWidth / 2)
        .attr("y", -this.nodeHeight / 2)
        .attr("width", this.nodeWidth)
        .attr("height", this.nodeHeight);

      inner
        .selectAll("g.edgePath path")
        .style("stroke", (d) => {
          if (injectedChaos?.params[0]?.value === d.v) {
            return this.getColorBystatus(injectedChaos.status);
          }
          return "#8bd496";
        })
        .style("stroke-width", 2);
      inner.selectAll("g.node text").attr("y", this.nodeHeight / 2 + 10);

      var svgWidth = +container.node().getBoundingClientRect().width;
      var svgHeight = +container.node().getBoundingClientRect().height;
      var zoomScale =
        0.9 *
        Math.min(svgWidth / g.graph().width, svgHeight / g.graph().height);
      var xCenterOffset = (svgWidth - g.graph().width * zoomScale) / 2;
      var yCenterOffset = (svgHeight - g.graph().height * zoomScale) / 2;
      inner.attr(
        "transform",
        "translate(" +
          xCenterOffset +
          "," +
          yCenterOffset +
          ") scale(" +
          zoomScale +
          ")"
      );
      svg.attr("width", svgWidth).attr("height", svgHeight);
      const allNodeNames = g.nodes();
      allNodeNames.forEach((nodeName: string) => {
        d3.select("g#node-" + nodeName + ".node")
          .on("click", function () {
            d3.selectAll("circle").interrupt();
            d3.selectAll("circle")
              .style("fill", "none")
              .style("stroke", "none")
              .style("stroke-width", "0");
            d3.select(this)
              .select("circle")
              .transition()
              .ease(d3.easeExpOut) // 缓动函数，可以根据需要调整
              .duration(1000) // 动画持续时间
              .style("fill", "rgba(255, 208, 0, 0.3)")
              .style("stroke", "rgba(255, 208, 0, 0.2)")
              .style("stroke-width", "15px");
            // .attr("r", 50) // 涟漪半径;
            vm.handleNodeClick(nodeName);
          })
          .on("mouseover", function (d) {
            d3.select(this).style("cursor", "pointer");
          });
      });
      let currentZoomTransform = d3.zoomTransform(this);
      // 缩放
      const zoom = d3.zoom().on("zoom", function () {
        let currentZoomTransform = d3.zoomTransform(this);
        inner.attr(
          "transform",
          `translate(${currentZoomTransform.x + xCenterOffset},${
            currentZoomTransform.y + yCenterOffset
          }) scale(${zoomScale + currentZoomTransform.k - 1})`
        );
      });
      svg.call(zoom);
    },
    changeRequest(value, option) {
      this.selectedRequset = option;
      this.requestData = this.getData(this.selectedRequsetValue);
      d3.select("#d3-container").select("svg").remove();
      this.createForceDirectedGraph();
      // 更新折线图
      useBusinessRequestModuleStore().setBusinessRequest(this.selectedRequset);
    },
    handleNodeClick(node: string) {
      eventBus.emit("click-instance", node);
      useInstanceModuleStore().setSelectedNode(node);
    },
    startPolling() {
      if (this.pollingInterval) {
        clearInterval(this.pollingInterval);
      }
      this.pollingInterval = setInterval(this.pollingFunction, 3000); //
    },
    async pollingFunction() {
      try {
        await this.fetchData();
      } catch (error) {
        console.error("Error during polling:", error);
        // clearInterval(this.pollingInterval);
      }
    },
    async fetchData() {
      try {
        const res = await getChaosStatusApi();
        if (res.code === "0") {
          const injectedChaosParams = this.injectedChaos?.params;
          const resChaosParams = res.data.chaos?.params;
          const resChaosStatus = res.data.chaos?.status;

          if (injectedChaosParams?.length > 0) {
            const injectedValue = injectedChaosParams[0].value;
            if (!res.data.chaos || injectedValue !== resChaosParams[0]?.value) {
              this.updateNodeAndLinks(injectedValue, "running");
            }
          }

          if (
            resChaosParams?.[0]?.value !==
              this.injectedChaos?.params[0]?.value ||
            this.injectedChaos?.status !== resChaosStatus
          ) {
            if (resChaosParams?.[0]?.value) {
              this.updateNodeAndLinks(resChaosParams[0].value, resChaosStatus);
            }
          }
          this.injectedChaos = res.data.chaos;
          useInstanceModuleStore().setInstance(this.injectedChaos);
        }
      } catch (error) {
        throw error;
      }
    },
    onClickButton() {
      const remainingUrl = localStorage.getItem("remainingUrl");
      const cheapestUrl = localStorage.getItem("cheapestUrl");
      if (this.selectedRequset.value === "remaining" && remainingUrl) {
        window.open(remainingUrl);
      } else if (this.selectedRequset.value === "cheapest" && cheapestUrl) {
        window.open(cheapestUrl);
      } else {
        this.openRequestSettingModal();
      }
    },
    openRequestSettingModal() {
      this.$refs.requestModalRef?.openModal();
    },
  },
};
</script>

<style lang="less" scoped>
.select-div {
  padding-left: 20px;
}
.topology-card :deep(.ant-card-body) {
  height: 100%;
  padding: 0;
}
.topology-card {
  display: flex;
  flex-direction: column;
}
g.node rect {
  fill: none;
  stroke: none;
} /* 定义放大动画 */
.node-selected {
  transform: scale(1.2); /* 放大到 1.2 倍 */
  transition: transform 0.3s ease; /* 添加过渡效果，持续时间可以根据需求调整 */
}
</style>